package com.gusto.engine.recommend.prediction.base;

import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;

import com.gusto.engine.colfil.Distance;
import com.gusto.engine.colfil.Evaluation;
import com.gusto.engine.colfil.Prediction;
import com.gusto.engine.colfil.Rating;
import com.gusto.engine.recommend.PredictionService;

/**
 * <p>Basic implementation for Collaborative algorithms.<p>
 * 
 * @see BaseCollaborativeImpl
 * @see BaseSemanticImpl
 * @see BaseHybridImpl
 * 
 * @author amokrane.belloui@gmail.com
 *
 */
public abstract class BaseCollaborativeImpl extends BaseImpl implements PredictionService {
	
	private Logger log = Logger.getLogger(getClass());
	
	protected Integer minEvals = 1;
	public void setMinEvals(Integer minEvals) {
		this.minEvals = minEvals;
	}
	
	protected Integer userMinShared, itemMinShared;
	public void setUserMinShared(Integer userMinShared) {
		this.userMinShared = userMinShared;
	}
	public void setItemMinShared(Integer itemMinShared) {
		this.itemMinShared = itemMinShared;
	}
	
	protected Double userMinSimilarity, itemMinSimilarity;
	public void setUserMinSimilarity(Double userMinSimilarity) {
		this.userMinSimilarity = userMinSimilarity;
	}
	public void setItemMinSimilarity(Double itemMinSimilarity) {
		this.itemMinSimilarity = itemMinSimilarity;
	}
	
	protected String colUserFormula = "(((%DISTANCE%+1)/2) * 0.85 + (round(%COMMON%*1.0/2)/5) * 0.15 )";
	protected String colItemFormula = "(((%DISTANCE%+1)/2) * 0.85 + (round(%COMMON%/3)/7) * 0.15 )";
	public void setColUserFormula(String colUserFormula) {
		this.colUserFormula = colUserFormula;
	}
	public void setColItemFormula(String colItemFormula) {
		this.colItemFormula = colItemFormula;
	}
	
	public Prediction predict(long userId, long itemId, boolean includePrediction) throws Exception {
		long start = System.currentTimeMillis();
		
		List<Distance> users = collaborativeNeighborhoodDelegate.getNearUsers(userId, colUserFormula, userMinShared, null, userMinSimilarity);
		List<Distance> items = collaborativeNeighborhoodDelegate.getNearItems(itemId, colItemFormula, itemMinShared, null, itemMinSimilarity);
		
		Map<Long, Double> usersWeights = this.buildUserWeights(userId, users);
		Map<Long, Double> itemsWeights = this.buildItemWeights(itemId, items);
		
		log.debug("Getting submatrix for " + usersWeights + " " + itemsWeights);
		List<Rating> evals = collaborativeService.getSubMatrix(usersWeights.keySet(), itemsWeights.keySet());
		log.debug("Submatrix contains " + evals.size() + " ratings");
		
		Double val = null;
		
		double user_mean = collaborativeService.getUserMeanRating(userId);
		double item_mean = collaborativeService.getItemMeanRating(itemId);
		// Calculate the mean evaluation for the Item (no neighborhood)
		
		val = doPrediction(evals, user_mean, item_mean, usersWeights, itemsWeights);
		
		logPrediction(0, 0, 0, (System.currentTimeMillis() - start), "PPPP");
		return returnPrediction(userId, itemId, val);
	}
	
	protected abstract Double doPrediction(List<? extends Evaluation> evals, double user_mean, double item_mean, Map<Long, Double> usersWeights, Map<Long, Double> itemsWeights);
	
}
